/*
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * The Original Code is Copyright (C) 2012 Blender Foundation.
 * All rights reserved.
 */

/** \file
 * \ingroup pybmesh
 *
 * This file defines custom-data types which can't be accessed as primitive
 * python types such as #MDeformVert, #MLoopUV.
 */

#include <Python.h>

#include "../mathutils/mathutils.h"

#include "DNA_object_types.h"
#include "DNA_meshdata_types.h"

#include "BLI_utildefines.h"
#include "BLI_math_base.h"
#include "BLI_math_vector.h"

#include "BKE_deform.h"

#include "bmesh_py_types_meshdata.h"

#include "../generic/py_capi_utils.h"
#include "../generic/python_utildefines.h"

/* Mesh Loop UV
 * ************ */

#define BPy_BMLoopUV_Check(v) (Py_TYPE(v) == &BPy_BMLoopUV_Type)

typedef struct BPy_BMLoopUV {
  PyObject_VAR_HEAD MLoopUV *data;
} BPy_BMLoopUV;

PyDoc_STRVAR(bpy_bmloopuv_uv_doc,
             "Loops UV (as a 2D Vector).\n\n:type: :class:`mathutils.Vector`");
static PyObject *bpy_bmloopuv_uv_get(BPy_BMLoopUV *self, void *UNUSED(closure))
{
  return Vector_CreatePyObject_wrap(self->data->uv, 2, NULL);
}

static int bpy_bmloopuv_uv_set(BPy_BMLoopUV *self, PyObject *value, void *UNUSED(closure))
{
  float tvec[2];
  if (mathutils_array_parse(tvec, 2, 2, value, "BMLoopUV.uv") != -1) {
    copy_v2_v2(self->data->uv, tvec);
    return 0;
  }
  else {
    return -1;
  }
}

PyDoc_STRVAR(bpy_bmloopuv_flag__pin_uv_doc, "UV pin state.\n\n:type: boolean");
PyDoc_STRVAR(bpy_bmloopuv_flag__select_doc, "UV select state.\n\n:type: boolean");

static PyObject *bpy_bmloopuv_flag_get(BPy_BMLoopUV *self, void *flag_p)
{
  const int flag = POINTER_AS_INT(flag_p);
  return PyBool_FromLong(self->data->flag & flag);
}

static int bpy_bmloopuv_flag_set(BPy_BMLoopUV *self, PyObject *value, void *flag_p)
{
  const int flag = POINTER_AS_INT(flag_p);

  switch (PyC_Long_AsBool(value)) {
    case true:
      self->data->flag |= flag;
      return 0;
    case false:
      self->data->flag &= ~flag;
      return 0;
    default:
      /* error is set */
      return -1;
  }
}

static PyGetSetDef bpy_bmloopuv_getseters[] = {
    /* attributes match rna_def_mloopuv  */
    {"uv", (getter)bpy_bmloopuv_uv_get, (setter)bpy_bmloopuv_uv_set, bpy_bmloopuv_uv_doc, NULL},
    {"pin_uv",
     (getter)bpy_bmloopuv_flag_get,
     (setter)bpy_bmloopuv_flag_set,
     bpy_bmloopuv_flag__pin_uv_doc,
     (void *)MLOOPUV_PINNED},
    {"select",
     (getter)bpy_bmloopuv_flag_get,
     (setter)bpy_bmloopuv_flag_set,
     bpy_bmloopuv_flag__select_doc,
     (void *)MLOOPUV_VERTSEL},

    {NULL, NULL, NULL, NULL, NULL} /* Sentinel */
};

PyTypeObject BPy_BMLoopUV_Type; /* bm.loops.layers.uv.active */

static void bm_init_types_bmloopuv(void)
{
  BPy_BMLoopUV_Type.tp_basicsize = sizeof(BPy_BMLoopUV);

  BPy_BMLoopUV_Type.tp_name = "BMLoopUV";

  BPy_BMLoopUV_Type.tp_doc = NULL;  // todo

  BPy_BMLoopUV_Type.tp_getset = bpy_bmloopuv_getseters;

  BPy_BMLoopUV_Type.tp_flags = Py_TPFLAGS_DEFAULT;

  PyType_Ready(&BPy_BMLoopUV_Type);
}

int BPy_BMLoopUV_AssignPyObject(struct MLoopUV *mloopuv, PyObject *value)
{
  if (UNLIKELY(!BPy_BMLoopUV_Check(value))) {
    PyErr_Format(PyExc_TypeError, "expected BMLoopUV, not a %.200s", Py_TYPE(value)->tp_name);
    return -1;
  }
  else {
    *((MLoopUV *)mloopuv) = *(((BPy_BMLoopUV *)value)->data);
    return 0;
  }
}

PyObject *BPy_BMLoopUV_CreatePyObject(struct MLoopUV *mloopuv)
{
  BPy_BMLoopUV *self = PyObject_New(BPy_BMLoopUV, &BPy_BMLoopUV_Type);
  self->data = mloopuv;
  return (PyObject *)self;
}

/* --- End Mesh Loop UV --- */

/* Mesh Vert Skin
 * ************ */

#define BPy_BMVertSkin_Check(v) (Py_TYPE(v) == &BPy_BMVertSkin_Type)

typedef struct BPy_BMVertSkin {
  PyObject_VAR_HEAD MVertSkin *data;
} BPy_BMVertSkin;

PyDoc_STRVAR(bpy_bmvertskin_radius_doc,
             "Vert skin radii (as a 2D Vector).\n\n:type: :class:`mathutils.Vector`");
static PyObject *bpy_bmvertskin_radius_get(BPy_BMVertSkin *self, void *UNUSED(closure))
{
  return Vector_CreatePyObject_wrap(self->data->radius, 2, NULL);
}

static int bpy_bmvertskin_radius_set(BPy_BMVertSkin *self, PyObject *value, void *UNUSED(closure))
{
  float tvec[2];
  if (mathutils_array_parse(tvec, 2, 2, value, "BMVertSkin.radius") != -1) {
    copy_v2_v2(self->data->radius, tvec);
    return 0;
  }
  else {
    return -1;
  }
}

PyDoc_STRVAR(bpy_bmvertskin_flag__use_root_doc,
             "Use as root vertex. Setting this flag does not clear other roots in the same mesh "
             "island.\n\n:type: boolean");
PyDoc_STRVAR(bpy_bmvertskin_flag__use_loose_doc, "Use loose vertex.\n\n:type: boolean");

static PyObject *bpy_bmvertskin_flag_get(BPy_BMVertSkin *self, void *flag_p)
{
  const int flag = POINTER_AS_INT(flag_p);
  return PyBool_FromLong(self->data->flag & flag);
}

static int bpy_bmvertskin_flag_set(BPy_BMVertSkin *self, PyObject *value, void *flag_p)
{
  const int flag = POINTER_AS_INT(flag_p);

  switch (PyC_Long_AsBool(value)) {
    case true:
      self->data->flag |= flag;
      return 0;
    case false:
      self->data->flag &= ~flag;
      return 0;
    default:
      /* error is set */
      return -1;
  }
}

static PyGetSetDef bpy_bmvertskin_getseters[] = {
    /* attributes match rna_mesh_gen  */
    {"radius",
     (getter)bpy_bmvertskin_radius_get,
     (setter)bpy_bmvertskin_radius_set,
     bpy_bmvertskin_radius_doc,
     NULL},
    {"use_root",
     (getter)bpy_bmvertskin_flag_get,
     (setter)bpy_bmvertskin_flag_set,
     bpy_bmvertskin_flag__use_root_doc,
     (void *)MVERT_SKIN_ROOT},
    {"use_loose",
     (getter)bpy_bmvertskin_flag_get,
     (setter)bpy_bmvertskin_flag_set,
     bpy_bmvertskin_flag__use_loose_doc,
     (void *)MVERT_SKIN_LOOSE},

    {NULL, NULL, NULL, NULL, NULL} /* Sentinel */
};

static PyTypeObject BPy_BMVertSkin_Type; /* bm.loops.layers.skin.active */

static void bm_init_types_bmvertskin(void)
{
  BPy_BMVertSkin_Type.tp_basicsize = sizeof(BPy_BMVertSkin);

  BPy_BMVertSkin_Type.tp_name = "BMVertSkin";

  BPy_BMVertSkin_Type.tp_doc = NULL;  // todo

  BPy_BMVertSkin_Type.tp_getset = bpy_bmvertskin_getseters;

  BPy_BMVertSkin_Type.tp_flags = Py_TPFLAGS_DEFAULT;

  PyType_Ready(&BPy_BMVertSkin_Type);
}

int BPy_BMVertSkin_AssignPyObject(struct MVertSkin *mvertskin, PyObject *value)
{
  if (UNLIKELY(!BPy_BMVertSkin_Check(value))) {
    PyErr_Format(PyExc_TypeError, "expected BMVertSkin, not a %.200s", Py_TYPE(value)->tp_name);
    return -1;
  }
  else {
    *((MVertSkin *)mvertskin) = *(((BPy_BMVertSkin *)value)->data);
    return 0;
  }
}

PyObject *BPy_BMVertSkin_CreatePyObject(struct MVertSkin *mvertskin)
{
  BPy_BMVertSkin *self = PyObject_New(BPy_BMVertSkin, &BPy_BMVertSkin_Type);
  self->data = mvertskin;
  return (PyObject *)self;
}

/* --- End Mesh Vert Skin --- */

/* Mesh Loop Color
 * *************** */

/* This simply provides a color wrapper for
 * color which uses mathutils callbacks for mathutils.Color
 */

#define MLOOPCOL_FROM_CAPSULE(color_capsule) \
  ((MLoopCol *)PyCapsule_GetPointer(color_capsule, NULL))

static void mloopcol_to_float(const MLoopCol *mloopcol, float r_col[3])
{
  rgba_uchar_to_float(r_col, (const uchar *)&mloopcol->r);
}

static void mloopcol_from_float(MLoopCol *mloopcol, const float col[3])
{
  rgba_float_to_uchar((uchar *)&mloopcol->r, col);
}

static uchar mathutils_bmloopcol_cb_index = -1;

static int mathutils_bmloopcol_check(BaseMathObject *UNUSED(bmo))
{
  /* always ok */
  return 0;
}

static int mathutils_bmloopcol_get(BaseMathObject *bmo, int UNUSED(subtype))
{
  MLoopCol *mloopcol = MLOOPCOL_FROM_CAPSULE(bmo->cb_user);
  mloopcol_to_float(mloopcol, bmo->data);
  return 0;
}

static int mathutils_bmloopcol_set(BaseMathObject *bmo, int UNUSED(subtype))
{
  MLoopCol *mloopcol = MLOOPCOL_FROM_CAPSULE(bmo->cb_user);
  mloopcol_from_float(mloopcol, bmo->data);
  return 0;
}

static int mathutils_bmloopcol_get_index(BaseMathObject *bmo, int subtype, int UNUSED(index))
{
  /* lazy, avoid repeteing the case statement */
  if (mathutils_bmloopcol_get(bmo, subtype) == -1) {
    return -1;
  }
  return 0;
}

static int mathutils_bmloopcol_set_index(BaseMathObject *bmo, int subtype, int index)
{
  const float f = bmo->data[index];

  /* lazy, avoid repeteing the case statement */
  if (mathutils_bmloopcol_get(bmo, subtype) == -1) {
    return -1;
  }

  bmo->data[index] = f;
  return mathutils_bmloopcol_set(bmo, subtype);
}

static Mathutils_Callback mathutils_bmloopcol_cb = {
    mathutils_bmloopcol_check,
    mathutils_bmloopcol_get,
    mathutils_bmloopcol_set,
    mathutils_bmloopcol_get_index,
    mathutils_bmloopcol_set_index,
};

static void bm_init_types_bmloopcol(void)
{
  /* pass */
  mathutils_bmloopcol_cb_index = Mathutils_RegisterCallback(&mathutils_bmloopcol_cb);
}

int BPy_BMLoopColor_AssignPyObject(struct MLoopCol *mloopcol, PyObject *value)
{
  float tvec[4];
  if (mathutils_array_parse(tvec, 4, 4, value, "BMLoopCol") != -1) {
    mloopcol_from_float(mloopcol, tvec);
    return 0;
  }
  else {
    return -1;
  }
}

PyObject *BPy_BMLoopColor_CreatePyObject(struct MLoopCol *data)
{
  PyObject *color_capsule;
  color_capsule = PyCapsule_New(data, NULL, NULL);
  return Vector_CreatePyObject_cb(color_capsule, 4, mathutils_bmloopcol_cb_index, 0);
}

#undef MLOOPCOL_FROM_CAPSULE

/* --- End Mesh Loop Color --- */

/* Mesh Deform Vert
 * **************** */

/**
 * This is python type wraps a deform vert as a python dictionary,
 * hiding the #MDeformWeight on access, since the mapping is very close, eg:
 *
 * \code{.c}
 * weight = BKE_defvert_find_weight(dv, group_nr);
 * BKE_defvert_remove_group(dv, dw)
 * \endcode
 *
 * \code{.py}
 * weight = dv[group_nr]
 * del dv[group_nr]
 * \endcode
 *
 * \note There is nothing BMesh specific here,
 * its only that BMesh is the only part of blender that uses a hand written api like this.
 * This type could eventually be used to access lattice weights.
 *
 * \note Many of blender-api's dict-like-wrappers act like ordered dicts,
 * This is intentionally _not_ ordered, the weights can be in any order and it won't matter,
 * the order should not be used in the api in any meaningful way (as with a python dict)
 * only expose as mapping, not a sequence.
 */

#define BPy_BMDeformVert_Check(v) (Py_TYPE(v) == &BPy_BMDeformVert_Type)

typedef struct BPy_BMDeformVert {
  PyObject_VAR_HEAD MDeformVert *data;
} BPy_BMDeformVert;

/* Mapping Protocols
 * ================= */

static int bpy_bmdeformvert_len(BPy_BMDeformVert *self)
{
  return self->data->totweight;
}

static PyObject *bpy_bmdeformvert_subscript(BPy_BMDeformVert *self, PyObject *key)
{
  if (PyIndex_Check(key)) {
    int i;
    i = PyNumber_AsSsize_t(key, PyExc_IndexError);
    if (i == -1 && PyErr_Occurred()) {
      return NULL;
    }
    else {
      MDeformWeight *dw = BKE_defvert_find_index(self->data, i);

      if (dw == NULL) {
        PyErr_SetString(PyExc_KeyError,
                        "BMDeformVert[key] = x: "
                        "key not found");
        return NULL;
      }
      else {
        return PyFloat_FromDouble(dw->weight);
      }
    }
  }
  else {
    PyErr_Format(
        PyExc_TypeError, "BMDeformVert keys must be integers, not %.200s", Py_TYPE(key)->tp_name);
    return NULL;
  }
}

static int bpy_bmdeformvert_ass_subscript(BPy_BMDeformVert *self, PyObject *key, PyObject *value)
{
  if (PyIndex_Check(key)) {
    int i;

    i = PyNumber_AsSsize_t(key, PyExc_IndexError);
    if (i == -1 && PyErr_Occurred()) {
      return -1;
    }

    if (value) {
      /* dvert[group_index] = 0.5 */
      if (i < 0) {
        PyErr_SetString(PyExc_KeyError,
                        "BMDeformVert[key] = x: "
                        "weight keys can't be negative");
        return -1;
      }
      else {
        MDeformWeight *dw = BKE_defvert_ensure_index(self->data, i);
        const float f = PyFloat_AsDouble(value);
        if (f == -1 && PyErr_Occurred()) {  // parsed key not a number
          PyErr_SetString(PyExc_TypeError,
                          "BMDeformVert[key] = x: "
                          "assigned value not a number");
          return -1;
        }

        dw->weight = clamp_f(f, 0.0f, 1.0f);
      }
    }
    else {
      /* del dvert[group_index] */
      MDeformWeight *dw = BKE_defvert_find_index(self->data, i);

      if (dw == NULL) {
        PyErr_SetString(PyExc_KeyError,
                        "del BMDeformVert[key]: "
                        "key not found");
      }
      BKE_defvert_remove_group(self->data, dw);
    }

    return 0;
  }
  else {
    PyErr_Format(
        PyExc_TypeError, "BMDeformVert keys must be integers, not %.200s", Py_TYPE(key)->tp_name);
    return -1;
  }
}

static int bpy_bmdeformvert_contains(BPy_BMDeformVert *self, PyObject *value)
{
  const int key = PyLong_AsSsize_t(value);

  if (key == -1 && PyErr_Occurred()) {
    PyErr_SetString(PyExc_TypeError, "BMDeformVert.__contains__: expected an int");
    return -1;
  }

  return (BKE_defvert_find_index(self->data, key) != NULL) ? 1 : 0;
}

/* only defined for __contains__ */
static PySequenceMethods bpy_bmdeformvert_as_sequence = {
    (lenfunc)bpy_bmdeformvert_len, /* sq_length */
    NULL,                          /* sq_concat */
    NULL,                          /* sq_repeat */

    /* Note: if this is set #PySequence_Check() returns True,
     * but in this case we don't want to be treated as a seq. */
    NULL, /* sq_item */

    NULL,                                  /* sq_slice */
    NULL,                                  /* sq_ass_item */
    NULL,                                  /* *was* sq_ass_slice */
    (objobjproc)bpy_bmdeformvert_contains, /* sq_contains */
    (binaryfunc)NULL,                      /* sq_inplace_concat */
    (ssizeargfunc)NULL,                    /* sq_inplace_repeat */
};

static PyMappingMethods bpy_bmdeformvert_as_mapping = {
    (lenfunc)bpy_bmdeformvert_len,
    (binaryfunc)bpy_bmdeformvert_subscript,
    (objobjargproc)bpy_bmdeformvert_ass_subscript,
};

/* Methods
 * ======= */

PyDoc_STRVAR(bpy_bmdeformvert_keys_doc,
             ".. method:: keys()\n"
             "\n"
             "   Return the group indices used by this vertex\n"
             "   (matching pythons dict.keys() functionality).\n"
             "\n"
             "   :return: the deform group this vertex uses\n"
             "   :rtype: list of ints\n");
static PyObject *bpy_bmdeformvert_keys(BPy_BMDeformVert *self)
{
  PyObject *ret;
  int i;
  MDeformWeight *dw = self->data->dw;

  ret = PyList_New(self->data->totweight);
  for (i = 0; i < self->data->totweight; i++, dw++) {
    PyList_SET_ITEM(ret, i, PyLong_FromLong(dw->def_nr));
  }

  return ret;
}

PyDoc_STRVAR(bpy_bmdeformvert_values_doc,
             ".. method:: values()\n"
             "\n"
             "   Return the weights of the deform vertex\n"
             "   (matching pythons dict.values() functionality).\n"
             "\n"
             "   :return: The weights that influence this vertex\n"
             "   :rtype: list of floats\n");
static PyObject *bpy_bmdeformvert_values(BPy_BMDeformVert *self)
{
  PyObject *ret;
  int i;
  MDeformWeight *dw = self->data->dw;

  ret = PyList_New(self->data->totweight);
  for (i = 0; i < self->data->totweight; i++, dw++) {
    PyList_SET_ITEM(ret, i, PyFloat_FromDouble(dw->weight));
  }

  return ret;
}

PyDoc_STRVAR(bpy_bmdeformvert_items_doc,
             ".. method:: items()\n"
             "\n"
             "   Return (group, weight) pairs for this vertex\n"
             "   (matching pythons dict.items() functionality).\n"
             "\n"
             "   :return: (key, value) pairs for each deform weight of this vertex.\n"
             "   :rtype: list of tuples\n");
static PyObject *bpy_bmdeformvert_items(BPy_BMDeformVert *self)
{
  PyObject *ret;
  PyObject *item;
  int i;
  MDeformWeight *dw = self->data->dw;

  ret = PyList_New(self->data->totweight);
  for (i = 0; i < self->data->totweight; i++, dw++) {
    item = PyTuple_New(2);
    PyTuple_SET_ITEMS(item, PyLong_FromLong(dw->def_nr), PyFloat_FromDouble(dw->weight));
    PyList_SET_ITEM(ret, i, item);
  }

  return ret;
}

PyDoc_STRVAR(bpy_bmdeformvert_get_doc,
             ".. method:: get(key, default=None)\n"
             "\n"
             "   Returns the deform weight matching the key or default\n"
             "   when not found (matches pythons dictionary function of the same name).\n"
             "\n"
             "   :arg key: The key associated with deform weight.\n"
             "   :type key: int\n"
             "   :arg default: Optional argument for the value to return if\n"
             "      *key* is not found.\n"
             "   :type default: Undefined\n");
static PyObject *bpy_bmdeformvert_get(BPy_BMDeformVert *self, PyObject *args)
{
  int key;
  PyObject *def = Py_None;

  if (!PyArg_ParseTuple(args, "i|O:get", &key, &def)) {
    return NULL;
  }
  else {
    MDeformWeight *dw = BKE_defvert_find_index(self->data, key);

    if (dw) {
      return PyFloat_FromDouble(dw->weight);
    }
    else {
      return Py_INCREF_RET(def);
    }
  }
}

PyDoc_STRVAR(bpy_bmdeformvert_clear_doc,
             ".. method:: clear()\n"
             "\n"
             "   Clears all weights.\n");
static PyObject *bpy_bmdeformvert_clear(BPy_BMDeformVert *self)
{
  BKE_defvert_clear(self->data);

  Py_RETURN_NONE;
}

static struct PyMethodDef bpy_bmdeformvert_methods[] = {
    {"keys", (PyCFunction)bpy_bmdeformvert_keys, METH_NOARGS, bpy_bmdeformvert_keys_doc},
    {"values", (PyCFunction)bpy_bmdeformvert_values, METH_NOARGS, bpy_bmdeformvert_values_doc},
    {"items", (PyCFunction)bpy_bmdeformvert_items, METH_NOARGS, bpy_bmdeformvert_items_doc},
    {"get", (PyCFunction)bpy_bmdeformvert_get, METH_VARARGS, bpy_bmdeformvert_get_doc},
    /* BMESH_TODO pop, popitem, update */
    {"clear", (PyCFunction)bpy_bmdeformvert_clear, METH_NOARGS, bpy_bmdeformvert_clear_doc},
    {NULL, NULL, 0, NULL},
};

PyTypeObject BPy_BMDeformVert_Type; /* bm.loops.layers.uv.active */

static void bm_init_types_bmdvert(void)
{
  BPy_BMDeformVert_Type.tp_basicsize = sizeof(BPy_BMDeformVert);

  BPy_BMDeformVert_Type.tp_name = "BMDeformVert";

  BPy_BMDeformVert_Type.tp_doc = NULL;  // todo

  BPy_BMDeformVert_Type.tp_as_sequence = &bpy_bmdeformvert_as_sequence;
  BPy_BMDeformVert_Type.tp_as_mapping = &bpy_bmdeformvert_as_mapping;

  BPy_BMDeformVert_Type.tp_methods = bpy_bmdeformvert_methods;

  BPy_BMDeformVert_Type.tp_flags = Py_TPFLAGS_DEFAULT;

  PyType_Ready(&BPy_BMDeformVert_Type);
}

int BPy_BMDeformVert_AssignPyObject(struct MDeformVert *dvert, PyObject *value)
{
  if (UNLIKELY(!BPy_BMDeformVert_Check(value))) {
    PyErr_Format(PyExc_TypeError, "expected BMDeformVert, not a %.200s", Py_TYPE(value)->tp_name);
    return -1;
  }
  else {
    MDeformVert *dvert_src = ((BPy_BMDeformVert *)value)->data;
    if (LIKELY(dvert != dvert_src)) {
      BKE_defvert_copy(dvert, dvert_src);
    }
    return 0;
  }
}

PyObject *BPy_BMDeformVert_CreatePyObject(struct MDeformVert *dvert)
{
  BPy_BMDeformVert *self = PyObject_New(BPy_BMDeformVert, &BPy_BMDeformVert_Type);
  self->data = dvert;
  return (PyObject *)self;
}

/* --- End Mesh Deform Vert --- */

/* call to init all types */
void BPy_BM_init_types_meshdata(void)
{
  bm_init_types_bmloopuv();
  bm_init_types_bmloopcol();
  bm_init_types_bmdvert();
  bm_init_types_bmvertskin();
}
